#include <core/common/log.h>
#include <core/common/string.h>

//
// class common::LogFile
//
core::common::LogFile::LogFile()
{
	type_   = core::common::Log::kLogTypeFile;
	handle_ = NULL;
	size_   = 0;
	beginTime_ = 0;
}

core::common::LogFile::~LogFile()
{
}

void core::common::LogFile::setType( uint32 type )
{
	close();

	type_ = type;
	switch( type_ )
	{
	case core::common::Log::kLogTypeStdout:
		handle_ = stdout;
		break;

	case core::common::Log::kLogTypeStderr:
		handle_ = stderr;
		break;

	default:
		break;
	}
}

bool core::common::LogFile::open( const std::string &file, bool append )
{
	close();

	type_ = core::common::Log::kLogTypeFile;
	file_ = file;

	handle_ = fopen(file_.c_str(), append ? "ab" : "wb");
	if( NULL == handle_ )
	{
		return false;
	}

	// seek to end
	fseek64(handle_, 0, SEEK_END);

	beginTime_ = File::getAttribue(file_.c_str()).createTime_;
	size_ = ftell64(handle_);

	return true;
}

bool core::common::LogFile::close()
{
	if( NULL == handle_ )
	{
		return false;
	}

	if( core::common::Log::kLogTypeFile == type_ )
	{
		fclose(handle_);
	}

	handle_ = NULL;
	return true;
}

int core::common::LogFile::write( core::common::Log *logger, uint32 level, const std::string &timeValue, const char *fmt, va_list argList )
{
	int result = 0;

	//
	// check log file first to prevent the last message lost after write,
	// because of the file maybe already been deleted or mark as deleted
	//
	if( core::common::Log::kLogTypeFile == type_ )
	{
		size_ = ftell64(handle_);

		if( size_ >= logger->sizeCapacity_ || beginTime_ + logger->timeCapacity_ <= time(NULL) )
		{
			// backup log file
			time_t timeNow = time(NULL);
			std::string beginTime = core::common::String::formatTime(beginTime_);
			std::string endTime = core::common::String::formatTime(timeNow);

			core::common::String::replace(beginTime, "-", ".");
			core::common::String::replace(beginTime, "/", ".");
			core::common::String::replace(beginTime, ":", ".");
			core::common::String::replace(beginTime, " ", "-");

			core::common::String::replace(endTime, "-", ".");
			core::common::String::replace(endTime, "/", ".");
			core::common::String::replace(endTime, ":", ".");
			core::common::String::replace(endTime, " ", "-");

			std::string backupFile = core::common::String::format("%s-(%s~%s)", file_.c_str(), beginTime.c_str(), endTime.c_str());

			fclose(handle_);
			handle_ = NULL;

			if( !core::common::File::moveFile(file_, backupFile) )
			{
				// fatal error on auto-process
				//fprintf(stderr, "Fatal error - backup log file(%s=>%s) failed (%u:%s)\r\n", 
				//	file_.c_str(), backupFile.c_str(), __UERR_CODE, __UERR_STR);
				//fflush(stderr);

				// try copy file if move failed
				// because of some program may is still opening the log file
				// such as using #tail -f xxx.log
				core::common::File::copyFile(file_, backupFile);
			}

			handle_ = fopen(file_.c_str(), "wb");
			if( NULL == handle_ )
			{
				// fatal error on auto-process
				fprintf(stderr, "Fatal error - reopen log file(%s) failed (%u:%s)\n", file_.c_str(), __UERR_CODE, __UERR_STR);
				fflush(stderr);
			}
			else
			{
#ifdef OS_PLATFORM_WINDOWS
				// fix Windows file's create time
				// the new log file's create time still keeps the original file time
				// we don't use copy() file because of move() is more quickly
				HANDLE win32Handle = (HANDLE)_get_osfhandle(fileno(handle_));
				if( INVALID_HANDLE_VALUE != win32Handle )
				{
					FILETIME currentTime = {0};
					::GetSystemTimeAsFileTime(&currentTime);
					::SetFileTime(win32Handle, &currentTime, &currentTime, &currentTime);
				}
#endif
				// seek to end
				fseek64(handle_, 0, SEEK_END);

				fprintf(handle_, "[%s - %s] Log::New file start, previous backup file is %s\r\n", 
					timeValue.c_str(), core::common::Log::getLevelName(Log::kLogLevelInfo), backupFile.c_str());
				fflush(handle_);

				beginTime_ = timeNow;
				size_ = ftell64(handle_);
			}
		}
	}

	if( NULL == handle_ )
	{
		return 0;
	}

	if( logger->autoAddTime_ )
	{
		result += fprintf(handle_, "[%s - %s] ", timeValue.c_str(), core::common::Log::getLevelName(level));
	}
	else
	{
		result += fprintf(handle_, "[%s]", core::common::Log::getLevelName(level));
	}

#ifdef OS_PLATFORM_WINDOWS
	result += vfprintf(handle_, fmt, argList);
#else
	// under linux, va_list can't been reused
	va_list copyArgs;
	va_copy(copyArgs, argList);
	result += vfprintf(handle_, fmt, copyArgs);
	va_end(copyArgs);
#endif

	if( logger->autoAddNewLine_ )
	{
		result += fprintf(handle_, "\r\n");
	}

	fflush(handle_);
	return result;
}

//
// class common::Log
//

core::common::Log::Log()
{
	autoAddTime_     = true;
	autoAddNewLine_  = true;
	milliSecondTime_ = true;
	multiOutput_     = true;
	type_ = core::common::Log::kLogTypeStdout | core::common::Log::kLogTypeStderr;
	level_ = core::common::Log::kLogLevelAll & ~core::common::Log::kLogLevelTrace;
	timeCapacity_ = core::common::kSecondUnitsPerDay;
	sizeCapacity_ = 10 * core::common::kByteUnitsPerMega;

	eventCallback_ = NULL;

	// attach stdout & stderr stream
	files[kLogTypeIdxStdout].setType(kLogTypeStdout);
	files[kLogTypeIdxStderr].setType(kLogTypeStderr);
}

core::common::Log::~Log()
{
	close();
}

bool core::common::Log::open( uint32 type, const std::string &file, bool append, bool multiOutput )
{
	// close exist file
	close();

	core::common::AutoLock __access__(&mutex_);

	type_ = type;
	file_ = file;
	multiOutput_ = multiOutput;
	
	if( !file_.empty() && (type_ & core::common::Log::kLogTypeFile) )
	{
		if( multiOutput_ )
		{
			std::string outFile = file + "-out";
			std::string errFile = file + "-err";
			std::string fileExt = core::common::File::getExtensionName(file);
			
			if( fileExt.size() > 0 && (fileExt.size() + 1) < file.size() )
			{
				std::string strSubPath = file.substr(0, file.size() - fileExt.size() - 1);

				outFile = core::common::String::format("%s-out.%s", strSubPath.c_str(), fileExt.c_str());
				errFile = core::common::String::format("%s-err.%s", strSubPath.c_str(), fileExt.c_str());
			}

			if( !files[kLogTypeIdxFileOut].open(outFile, append) )
			{
				return false;
			}

			if( !files[kLogTypeIdxFileErr].open(errFile, append) )
			{
				return false;
			}
		}
		else
		{
			if( !files[kLogTypeIdxFileOut].open(file, append) )
			{
				return false;
			}
		}
	}

	return true;
}

bool core::common::Log::close()
{
	core::common::AutoLock __access__(&mutex_);

	files[kLogTypeIdxFileOut].close();
	files[kLogTypeIdxFileErr].close();

	return true;
}

void core::common::Log::setEventCallback( core::common::LogEventCallback callback )
{
	eventCallback_ = callback;
}

uint64 core::common::Log::setSizeCapacity( uint64 size )
{
	uint64 nOldCapacity = sizeCapacity_;

	sizeCapacity_ = size;
	return nOldCapacity;
}

uint32 core::common::Log::setTimeCapacity( uint32 timeValue )
{
	uint32 nOldCapacity = (uint32)timeCapacity_;

	timeCapacity_ = (time_t)timeValue;
	return nOldCapacity;
}

bool core::common::Log::setTimeMilliSeconds( bool milliSeconds )
{
	bool oldMilli = milliSecondTime_;

	milliSecondTime_ = milliSeconds;
	return oldMilli;
}

uint32 core::common::Log::setLevel( uint32 level )
{
	uint32 oldLevel = level_;

	level_ = level;
	return oldLevel;
}

uint32 core::common::Log::getLevel() const
{
	return level_;
}

uint32 core::common::Log::setType( uint32 type )
{
	uint32 oldType = type_;

	type_ = type;
	return oldType;
}

uint32 core::common::Log::getType() const
{
	return type_;
}

void core::common::Log::setAutoProperty( bool addTime, bool addNewLine )
{
	autoAddTime_ = addTime;
	autoAddNewLine_ = addNewLine;
}

bool core::common::Log::isAutoAddTime() const
{
	return autoAddTime_;
}

bool core::common::Log::isAutoAddNewLine() const
{
	return autoAddNewLine_;
}

int core::common::Log::write( uint32 type, uint32 level, const char *fmt, va_list argList )
{
	if( level != kLogLevelAll && (level & level_) == kLogLevelNone )
	{
		return 0;
	}

	core::common::AutoLock __access__(&mutex_);

	int result = 0;
	std::string timeString = milliSecondTime_ ? core::common::String::getLocalTime(true) : core::common::String::formatTime(time(NULL));
	core::common::LogFile &logFile = files[(level <= kLogLevelInfo || !multiOutput_) ? kLogTypeIdxFileOut : kLogTypeIdxFileErr];
	core::common::LogFile &logStd  = files[(level <= kLogLevelInfo || !multiOutput_) ? kLogTypeIdxStdout : kLogTypeIdxStderr];
	
	if( logFile.isOpend() && (type & logFile.getType()) )
	{
		result += logFile.write(this, level, timeString, fmt, argList);
	}

	if( logStd.isOpend() && (type & logStd.getType()) )
	{
		result += logStd.write(this, level, timeString, fmt, argList);
	}

	return result;
}

int core::common::Log::trace( const char *fmt, ... )
{
	uint32 level = core::common::Log::kLogLevelTrace;
	int result = 0;

	va_list argList;	
	va_start(argList, fmt);
	result = write(type_, level, fmt, argList);
	va_end(argList);

	if( NULL != eventCallback_ && (level_ & level) != kLogLevelNone )
	{
		va_list pEventArgs;
		va_start(pEventArgs, fmt);
		eventCallback_(level, fmt, pEventArgs);
		va_end(pEventArgs);
	}

	return result;
}

int core::common::Log::info( const char *fmt, ... )
{
	uint32 level = core::common::Log::kLogLevelInfo;
	int result = 0;

	va_list argList;	
	va_start(argList, fmt);
	result = write(type_, level, fmt, argList);
	va_end(argList);

	if( NULL != eventCallback_ && (level_ & level) != kLogLevelNone )
	{
		va_list eventArgs;
		va_start(eventArgs, fmt);
		eventCallback_(level, fmt, eventArgs);
		va_end(eventArgs);
	}

	return result;
}

int core::common::Log::warning( const char *fmt, ... )
{
	uint32 level = core::common::Log::kLogLevelWarning;
	int result = 0;

	va_list argList;	
	va_start(argList, fmt);
	result = write(type_, level, fmt, argList);
	va_end(argList);

	if( NULL != eventCallback_ && (level_ & level) != kLogLevelNone )
	{
		va_list eventArgs;
		va_start(eventArgs, fmt);
		eventCallback_(level, fmt, eventArgs);
		va_end(eventArgs);
	}

	return result;
}

int core::common::Log::error( const char *fmt, ... )
{
	uint32 level = core::common::Log::kLogLevelError;
	int result = 0;

	va_list argList;	
	va_start(argList, fmt);
	result = write(type_, level, fmt, argList);
	va_end(argList);

	if( NULL != eventCallback_ && (level_ & level) != kLogLevelNone )
	{
		va_list eventArgs;
		va_start(eventArgs, fmt);
		eventCallback_(level, fmt, eventArgs);
		va_end(eventArgs);
	}

	return result;
}

int core::common::Log::fatal( const char *fmt, ... )
{
	uint32 level = core::common::Log::kLogLevelFatal;
	int result = 0;

	va_list argList;	
	va_start(argList, fmt);
	result = write(type_, level, fmt, argList);
	va_end(argList);

	if( NULL != eventCallback_ && (level_ & level) != kLogLevelNone )
	{
		va_list eventArgs;
		va_start(eventArgs, fmt);
		eventCallback_(level, fmt, eventArgs);
		va_end(eventArgs);
	}

	core::common::exit(0);
	return result;
}

int core::common::Log::writeErrorConsole( const char *fmt, ... )
{
	uint32 level = core::common::Log::kLogLevelError;
	int result = 0;

	// under Linux, va_list can't been reused
	va_list argList;	
	va_start(argList, fmt);
	result = write(core::common::Log::kLogTypeStderr, core::common::Log::kLogLevelAll, fmt, argList);
	va_end(argList);

	if( NULL != eventCallback_ && (level_ & level) != kLogLevelNone )
	{
		va_list eventArgs;
		va_start(eventArgs, fmt);
		eventCallback_(level, fmt, eventArgs);
		va_end(eventArgs);
	}

	return result;
}

bool core::common::Log::getBackupNameTime( const std::string &backupName, time_t &timeFrom, time_t &timeTo )
{
	size_t beginPos = backupName.find('(');
	size_t midPos   = backupName.find('~');
	size_t endPos   = backupName.rfind(')');

	if( beginPos == std::string::npos || endPos == std::string::npos || midPos == std::string::npos ||
		!(midPos > beginPos && midPos < endPos) )
	{
		return false;
	}

	std::string logTimeFrom = backupName.substr(beginPos + 1, midPos - beginPos - 1);
	std::string logTimeTo   = backupName.substr(midPos + 1, endPos - midPos - 1);

	timeFrom = core::common::String::toTimestamp(logTimeFrom);
	timeTo   = core::common::String::toTimestamp(logTimeTo);
	return true;
}

